#include "stdafx.h"
#include "D3DRenderer.h"

namespace Renderer
{
	//--------------------------------------------------------------------
	//--------------------------------------------------------------------
	//--------------------------------------------------------------------

	D3DEffect::D3DEffect(D3DRenderer& _rRenderer)
	:	m_rRenderer(_rRenderer),
		m_pEffect(NULL)
	{

	}

	D3DEffect::~D3DEffect()
	{

	}

	bool D3DEffect::Load(const wstring& _wstrFilename)
	{
		bool bResult = false;

		if ( NULL == m_pEffect )
		{
			DWORD dwShaderFlags = 0;

			//dwShaderFlags |= D3DXSHADER_FORCE_VS_SOFTWARE_NOOPT;
			//dwShaderFlags |= D3DXSHADER_FORCE_PS_SOFTWARE_NOOPT;
			//dwShaderFlags |= D3DXSHADER_NO_PRESHADER;
			dwShaderFlags |= D3DXSHADER_DEBUG;
			dwShaderFlags |= D3DXSHADER_SKIPOPTIMIZATION;

			HRESULT hResult = D3DXCreateEffectFromFile(
				m_rRenderer.GetDevice(),
				_wstrFilename.c_str(),
				NULL, // CONST D3DXMACRO* pDefines,
				NULL, // LPD3DXINCLUDE pInclude,
				dwShaderFlags,
				NULL, // LPD3DXEFFECTPOOL pPool,
				&m_pEffect,
				NULL );

			bResult = SUCCEEDED( hResult );

			if ( false != bResult )
			{
				GetParameters();
			}
		}

		return bResult;
	}

	void D3DEffect::Unload()
	{
		if ( NULL != m_pEffect )
		{
			m_pEffect->Release();
			m_pEffect = NULL;
		}
	}

	LPD3DXEFFECT D3DEffect::Get()
	{
		return m_pEffect;
	}

	void D3DEffect::GetParameters()
	{
		InitWORLDVIEWPROJ();
		InitVIEW();
		InitWORLD();
		InitZFAR();
		InitZNEAR();
	}

	//--------------------------------------------------------------------
	//--------------------------------------------------------------------
	//--------------------------------------------------------------------

	D3DRenderer::D3DRenderer(HWND _hWnd)
	:	m_hWnd(_hWnd),
		m_pD3D(NULL),
		m_pDevice(NULL),
		m_mRenderList(),
		m_mEffects(),
		m_fZNear(1.0f),
		m_fZFar(1000000.0f),
		m_fPixelSize(1.0f),
		m_oCamRot(0.0f, -90.0f, 0.0f),
		m_oCamPos(0.0f, 0.0f, 0.0f),
		m_oCameraRotation(),
		m_oCameraPosition(),
		m_oWorld(),
		m_oView(),
		m_oProj(),
		m_oInvView(),
		m_oViewProj(),
		m_oWorldViewProj()
	{
		UpdateCamera( true, true );
	}

	D3DRenderer::~D3DRenderer()
	{
		if ( NULL != m_pD3D )
		{
			m_pD3D->Release();
			m_pD3D = NULL;
		}
	}

	bool D3DRenderer::OpenVideo(const VideoInfo& _rInfo)
	{
		bool bResult = false;

		if ( NULL == m_pD3D )
		{
			m_pD3D = Direct3DCreate9( D3D_SDK_VERSION );
		}
		if ( NULL != m_pD3D )
		{
			if ( NULL == m_pDevice )
			{
				D3DPRESENT_PARAMETERS oD3DPP;
				ZeroMemory( &oD3DPP, sizeof( oD3DPP ) );
				oD3DPP.EnableAutoDepthStencil = TRUE;
				oD3DPP.AutoDepthStencilFormat = D3DFMT_D16;
				oD3DPP.BackBufferWidth = _rInfo.m_uWidth;
				oD3DPP.BackBufferHeight = _rInfo.m_uHeight;
				oD3DPP.Windowed = _rInfo.m_bFullscreen ? FALSE : TRUE;
				oD3DPP.SwapEffect = D3DSWAPEFFECT_DISCARD;
				oD3DPP.BackBufferFormat = D3DFMT_UNKNOWN;

				HRESULT hResult = m_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &oD3DPP, &m_pDevice );
				bResult = SUCCEEDED( hResult );
				if ( false == bResult )
				{
					CloseVideo();
				}

				SetDefautSettings( _rInfo );
			}
		}

		return bResult;
	}

	void D3DRenderer::CloseVideo()
	{
		if ( NULL != m_pDevice )
		{
			m_pDevice->Release();
			m_pDevice = NULL;
		}
	}

	void D3DRenderer::SetDefautSettings(const VideoInfo& _rInfo)
	{
		D3DCAPS9 d3dCaps;
		m_pDevice->GetDeviceCaps (&d3dCaps);

		// projection matrix
		m_fFovy = 45.0f * s_DegToRad;
		m_fAspect = (float)_rInfo.m_uWidth / (float)_rInfo.m_uHeight;
		D3DXMatrixPerspectiveFovLH( &m_oProj, m_fFovy, m_fAspect, m_fZNear, m_fZFar );

		// Recompute this one each time its dependencies change.
		// Credits to Celestia project. http://www.shatters.net/celestia/
		m_fPixelSize = 2.0f * (float) tan( m_fFovy / 2.0f ) / (float)_rInfo.m_uHeight;

		// states
		m_pDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CW );
		if (d3dCaps.FVFCaps & D3DFVFCAPS_PSIZE)
		{
			const float fMaxPointSize = ( 5.0f < d3dCaps.MaxPointSize ) ? 5.0f : d3dCaps.MaxPointSize;
			m_pDevice->SetRenderState( D3DRS_POINTSIZE, *((DWORD*)&fMaxPointSize ));
		}

		// view port
		D3DVIEWPORT9 oViewport;
		oViewport.Height = _rInfo.m_uHeight;
		oViewport.MaxZ = 1.0f;
		oViewport.MinZ = 0.0f;
		oViewport.Width = _rInfo.m_uWidth;
		oViewport.X = 0;
		oViewport.Y = 0;
		HRESULT hResult = m_pDevice->SetViewport( &oViewport );
	}

	bool D3DRenderer::LoadEffect(const string& _strName, const wstring& _wstrFilename)
	{
		bool bResult = false;
		if ( m_mEffects.end() == m_mEffects.find( _strName ) )
		{
			D3DEffectPtr pEffect = new D3DEffect( *this );
			bResult = pEffect->Load( _wstrFilename );
			if ( false != bResult )
			{
				m_mEffects[_strName] = pEffect ;
			}
		}
		return bResult;
	}

	void D3DRenderer::UnloadEffect(const string& _strName)
	{
		EffectMap::iterator iPair = m_mEffects.find( _strName );
		if ( m_mEffects.end() != iPair )
		{
			iPair->second->Unload();
			delete iPair->second;
			m_mEffects.erase( iPair );
		}
	}

	D3DEffectPtr D3DRenderer::GetEffect(const string& _strName)
	{
		EffectMap::iterator iPair = m_mEffects.find( _strName );
		if ( m_mEffects.end() != iPair )
		{
			return iPair->second;
		}
		return NULL;
	}

	LPDIRECT3DDEVICE9 D3DRenderer::GetDevice()
	{
		return m_pDevice;
	}

	const float& D3DRenderer::GetPixelSize() const
	{
		return m_fPixelSize;
	}

	void D3DRenderer::GetCameraDir(Vector3& _oFrontDir, Vector3& _oRightDir, Vector3& _oUpDir, const bool _bInv)
	{
		Matrix& rMatrix = ( false == _bInv ) ? m_oView : m_oInvView;

		_oFrontDir.x = rMatrix._13;
		_oFrontDir.y = rMatrix._23;
		_oFrontDir.z = rMatrix._33;

		_oRightDir.x = rMatrix._11;
		_oRightDir.y = rMatrix._21;
		_oRightDir.z = rMatrix._31;

		_oUpDir.x = rMatrix._12;
		_oUpDir.y = rMatrix._22;
		_oUpDir.z = rMatrix._32;
	}

	Vector3& D3DRenderer::GetCameraRot()
	{
		return m_oCamRot;
	}

	Vector3& D3DRenderer::GetCameraPos()
	{
		return m_oCamPos;
	}

	const Matrix& D3DRenderer::GetViewMatrix(const bool _bInv) const
	{
		if ( false != _bInv )
		{
			return m_oInvView;
		}
		return m_oView;
	}

	const Matrix& D3DRenderer::GetViewProjMatrix() const
	{
		return m_oViewProj;
	}

	void D3DRenderer::UpdateCamera(const bool& _bRotChanged, const bool& _bPosChanged)
	{
		if ( false != _bRotChanged )
		{
			static Matrix oXRot;
			static Matrix oYRot;
			static Matrix oZRot;
			static Matrix oTemp;
			D3DXMatrixRotationX( &oXRot, m_oCamRot.x * s_DegToRad );
			D3DXMatrixRotationY( &oYRot, m_oCamRot.y * s_DegToRad );
			D3DXMatrixRotationZ( &oZRot, m_oCamRot.z * s_DegToRad );
			D3DXMatrixMultiply( &oTemp, &oXRot, &oYRot );
			D3DXMatrixMultiply( &m_oCameraRotation, &oTemp, &oZRot );
		}
		if ( false != _bPosChanged )
		{
			D3DXMatrixTranslation( &m_oCameraPosition, m_oCamPos.x, m_oCamPos.y, m_oCamPos.z );
		}

		if ( ( false != _bRotChanged ) || ( false != _bPosChanged ) )
		{
			D3DXMatrixMultiply( &m_oView, &m_oCameraRotation, &m_oCameraPosition );
			D3DXMatrixMultiply( &m_oViewProj, D3DXMatrixInverse( &m_oInvView, NULL, &m_oView ), &m_oProj );
		}
	}

	void D3DRenderer::RenderRequest(Renderable* _pRenderable, const string& _strEffectName, const float _fZNear, const float _fZFar)
	{
		D3DEffectPtr pEffect = GetEffect( _strEffectName );
		if ( NULL != pEffect )
		{
			RenderInfo oInfo;
			m_mRenderList[pEffect].push_back( oInfo );
			RenderInfo& rInfo = m_mRenderList[pEffect].back();
			rInfo.m_pRenderable = _pRenderable;
			rInfo.m_fZNear = ( 0.0f <= _fZNear ) ? _fZNear : 0.0f;
			rInfo.m_fZFar = ( rInfo.m_fZNear < _fZFar ) ? _fZFar : rInfo.m_fZNear + 1000000.0f;
		}
	}

	void D3DRenderer::Begin()
	{
		HRESULT hResult = m_pDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB( 64, 0, 128 ), 1.0f, 0 );
		hResult = m_pDevice->BeginScene();
	}

	void D3DRenderer::Process()
	{
		RenderableMap::iterator iPair = m_mRenderList.begin();
		RenderableMap::iterator iEnd = m_mRenderList.end();

		while ( iEnd != iPair )
		{
			D3DEffectPtr pEffect = iPair->first;
			RenderInfoVec& vRenderList = iPair->second;
			if ( false == vRenderList.empty() )
			{
				RenderEffect( pEffect, vRenderList );
				// all objects to render have been processed : clear list.
				vRenderList.clear();
			}
			++iPair;
		}
	}

	void D3DRenderer::End()
	{
		m_pDevice->EndScene();
		m_pDevice->Present( NULL, NULL, NULL, NULL );
		m_mRenderList.clear();
	}

	void D3DRenderer::RenderEffect(D3DEffectPtr _pEffect, RenderInfoVec& _vRenderList)
	{
		LPD3DXEFFECT pEffect = _pEffect->Get();
		D3DXHANDLE hTech = pEffect->GetTechniqueByName( "RenderScene" );
		pEffect->SetTechnique( hTech );

		unsigned int uPassesCount;
		pEffect->Begin( &uPassesCount, 0 );

		#pragma message(__FUNCTION__" : store D3DXHANDLE into a dedicated struct and link them to related effect through a map<LPD3DXEFFECT, D3DXHANDLESTRUCT>")

		_pEffect->SetVIEW( &m_oView );
		// Set default depth values
		_pEffect->SetZNEAR( m_fZNear );
		_pEffect->SetZFAR( m_fZFar );

		RenderInfoVec::iterator iEnd = _vRenderList.end();

		{
			RenderInfoVec::iterator iRenderInfo = _vRenderList.begin();
			while ( iEnd != iRenderInfo )
			{
				RenderInfo& rInfo = *iRenderInfo;
				Renderable* pRenderable = rInfo.m_pRenderable;
				pRenderable->Begin();
				++iRenderInfo;
			}
		}

		D3DXPASS_DESC oPassDesc;

		for ( unsigned int uPass = 0 ; uPassesCount > uPass ; ++uPass )
		{
			RenderInfoVec::iterator iRenderInfo = _vRenderList.begin();

			pEffect->BeginPass( uPass );
			pEffect->GetPassDesc( pEffect->GetPass( hTech, uPass ), &oPassDesc );

			while ( iEnd != iRenderInfo )
			{
				RenderInfo& rInfo = *iRenderInfo;
				Renderable* pRenderable = rInfo.m_pRenderable;

				// Set renderable specific depth values
				if ( ( 0.0f <= rInfo.m_fZNear ) && ( rInfo.m_fZNear < rInfo.m_fZFar ) )
				{
					D3DXMatrixPerspectiveFovLH( &m_oProj,  m_fFovy, m_fAspect, rInfo.m_fZNear, rInfo.m_fZFar );
					D3DXMatrixMultiply( &m_oViewProj, &m_oInvView, &m_oProj );
					_pEffect->SetZNEAR( rInfo.m_fZNear );
					_pEffect->SetZFAR( rInfo.m_fZFar );
				}

				// enable object to render to modify effect parameters
				pRenderable->PreRender( _pEffect, oPassDesc );

				// set world*view*projection matrix
				m_oWorld = pRenderable->GetWorldTransform();
				_pEffect->SetWORLD( &m_oWorld );
				D3DXMatrixMultiply( &m_oWorldViewProj, &m_oWorld, &m_oViewProj );
				_pEffect->SetWORLDVIEWPROJ( &m_oWorldViewProj );

				pEffect->CommitChanges();

				pRenderable->Render();

				++iRenderInfo;
			}

			pEffect->EndPass();
		}

		{
			RenderInfoVec::iterator iRenderInfo = _vRenderList.begin();
			while ( iEnd != iRenderInfo )
			{
				RenderInfo& rInfo = *iRenderInfo;
				Renderable* pRenderable = rInfo.m_pRenderable;
				pRenderable->End();
				++iRenderInfo;
			}
		}

		pEffect->End();

	}
}